# -*- coding: utf-8 -*-
"""
系统级命令操作
@author:   XueJunJie
@file:     BrowSettings.py
@date:     2024-9-23
@version:  1.2.1
"""
import os
import re
import json
import shutil
import getpass
import zipfile
import platform
import requests
import warnings
from typing import Optional
from .. import _f_selenium_version_, _s_selenium_version_

if all([_f_selenium_version_ >= 4, _s_selenium_version_ >= 7]):
    # SeleniumManager 仅在4.7版本后自带[注:此方法可能需要翻墙,否则很慢或者下载文件失败]
    from selenium.webdriver.common.selenium_manager import SeleniumManager


from ..BrowSettings.BrowInfoFetch import get_chrome_path, get_chrome_version, fetch_user_data_dir, get_chrome_version_by_error, get_driver_version


def _fetch_npmmirror_driver_info(chrome_version: int, proxy=None):
    """
    提取npmmirror上对应driver信息（对于稳定版chrome）
    :chrome_version           int            指定的chrome版本,影响driver版本
    :proxy                    dict           下载时使用的代理
    res:
        flag                  bool           是否成功下载
        zip_url               str            zip文件下载url
        driverfile_in_zip     str            driver在zip里的位置
        remove_dirs           str            需要删除的缓存目录
    """
    base_url = "https://registry.npmmirror.com/-/binary/chromedriver/"
    try:
        html = requests.get(base_url, verify=True, timeout=10, proxies=proxy).text
    except:
        print("无法访问https://registry.npmmirror.com/-/binary/chromedriver/获取driver信息失败")
        return False, "", "", ""
    try:
        webdriver_data_map = json.loads(html)
    except Exception as e:
        print("无法加载新chromedriver页面")
        return False, "", "", ""
    target_webdriver_data = None
    for webdriver_data in webdriver_data_map[::-1]:
        if re.findall(f"^{chrome_version}\.[\d\.]+?", webdriver_data["name"]):
            target_webdriver_data = webdriver_data
            break
    if not target_webdriver_data:
        print("没有对应driver")
        return False, "", "", ""
    res = requests.get(webdriver_data["url"], proxies=proxy).text
    try:
        file_detail_data_map = json.loads(res)
    except Exception as e:
        print("无法加载新chromedriver详情页面")
        return False, "", "", ""
    target_file_data = None
    for file_detail_data in file_detail_data_map:
        if re.findall("win32\.zip", file_detail_data["name"]):
            target_file_data = file_detail_data
            break
    if not target_file_data:
        print("对应版本没有可下载的文件")
        return False, "", "", ""
    zip_url = target_file_data["url"]
    driverfile_in_zip = "chromedriver.exe"
    remove_dirs = []
    print("检测到对应driver信息")
    return True, zip_url, driverfile_in_zip, remove_dirs


def _fetch_googlechromelabs_driver_info(chrome_version: int, proxy=None):
    """
    提取googlechromelabs上对应driver信息(对于test版chrome的driver)
    :chrome_version           int            指定的chrome版本,影响driver版本
    :proxy                    dict           使用的代理
    res:
        flag                  bool           是否成功下载
        zip_url               str            zip文件下载url
        driverfile_in_zip     str            driver在zip里的位置
        remove_dirs           str            需要删除的缓存目录
    """
    base_url = "https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json"
    try:
        html = requests.get(base_url, verify=True, timeout=10, proxies=proxy).text
    except:
        print("无法访问https://googlechromelabs.github.io/chrome-for-testing/")
        return False, "", "", ""
    try:
        webdriver_data_map = json.loads(html)
    except Exception as e:
        print("无法加载新chromedriver页面")
        return False, "", "", ""
    target_webdriver_data = None
    for webdriver_data in webdriver_data_map["versions"][::-1]:
        if re.findall(f"^{chrome_version}\.[\d\.]+?", webdriver_data["version"]):
            _target_webdriver_data = webdriver_data
        else:
            continue
        # 寻求版本信息一致，且有driver的版本
        if "chromedriver" in _target_webdriver_data["downloads"].keys():
            target_webdriver_data = _target_webdriver_data
            break
    if not target_webdriver_data:
        print("没有对应driver")
        return False, "", "", ""
    target_file_data = None
    for file_detail_data in target_webdriver_data["downloads"]["chromedriver"]:
        if re.findall("win32\.zip", file_detail_data["url"]):
            target_file_data = file_detail_data
            break
    if not target_file_data:
        print("对应版本没有可下载的文件")
        return False, "", "", ""
    zip_url = target_file_data["url"]
    driverfile_in_zip = r"chromedriver-win32/chromedriver.exe"
    remove_dirs = ["chromedriver-win32"]
    print("检测到对应driver信息")
    return True, zip_url, driverfile_in_zip, remove_dirs


def update_chromedriver(chrome_filepath: str = "auto", driver_filepath: str = "chromedriver.exe", chrome_version=0, proxy=None) -> Optional[str]:
    """
    根据chrome版本更新driver版本(不支持70以下版本)
    :chrome_filepath    str    chrome启动文件
    :driver_filepath    str    driver启动文件(文件可以不存在)
    :chrome_version     int    指定的chrome版本,影响driver版本
    :proxy              dict     下载时使用的代理
    res:
        str/None         driver路径
    """
    chrome_version = 0
    driver_version = 0
    # 检测chrome
    if not chrome_version:
        if chrome_filepath == "auto":
            chrome_ver = get_chrome_version()
        else:
            chrome_dir, _ = os.path.split(chrome_filepath)
            chrome_ver = get_chrome_version(chrome_dir)
        chrome_version = int(chrome_ver.split(".")[0])
    else:
        if type(chrome_version) == str:
            chrome_version = int(chrome_version.split(".")[0])
        else:
            chrome_version = chrome_version
    if not chrome_version:
        print("chrome目录为未知目录结构")
        return None
    # 检测driver
    driver_dir, driver_name = os.path.split(driver_filepath)
    if driver_dir and not os.path.isdir(driver_dir):
        print("driver目录为非法目录")
        return None
    if os.path.isfile(driver_filepath):
        driver_ver = get_driver_version(driver_filepath)
        driver_version = int(driver_ver.split(".")[0])
    print(f"检测到chrome版本为{chrome_version}，driver版本为{driver_version}")
    if chrome_version == driver_version:
        print("版本匹配")
        return driver_filepath
    print("正在获取chromedriver信息")
    flag = None
    for fetch_fun in [_fetch_npmmirror_driver_info, _fetch_googlechromelabs_driver_info]:
        print(f"尝试使用方法{fetch_fun.__name__}")
        flag, zip_url, driverfile_in_zip, remove_dirs = fetch_fun(chrome_version, proxy)
        if flag:
            break
    if not flag:
        print(f"没有匹配到chrome版本{chrome_version}对应的driver信息")
        return None
    _, zip_filename = os.path.split(zip_url)
    # 下载对应zip
    print("正在下载zip文件")
    try:
        res = requests.get(zip_url, timeout=15, proxies=proxy).content
    except:
        print("无法下载对应driver文件，请检测网络")
        return None
    with open(zip_filename, "wb") as f:
        f.write(res)
        f.close()
    # 解压zip
    print("正在解压文件")
    z = zipfile.ZipFile(zip_filename, "r")
    try:
        z.extract(driverfile_in_zip, driver_dir)
    except Exception as e:
        if type(e) in (PermissionError,):
            kill_chrome_driver(True, driver_name)
            z.extract(driverfile_in_zip, driver_dir)
        else:
            raise e
    z.close()
    # 这目录才是文件的真正输出文件
    driverfile_out_zip = os.path.join(driver_dir, driverfile_in_zip)
    if not os.path.isfile(driver_filepath) or not os.path.samefile(driver_filepath, driverfile_out_zip):
        if os.path.isfile(driver_filepath):
            try:
                os.remove(driver_filepath)
            except Exception as e:
                if type(e) in (PermissionError,):
                    kill_chrome_driver(True, driver_name)
                    os.remove(driver_filepath)
                else:
                    raise e
        os.rename(driverfile_out_zip, os.path.join(driver_dir, driver_name))
    # 删除下载的文件，保持项目干净
    os.remove(zip_filename)
    for _dir in remove_dirs:
        try:
            _r_dir = os.path.join(driver_dir, _dir)
            os.rmdir(_r_dir)
        except:
            pass
    print("更新完成")
    return driver_filepath


def update_chromedriver_by_selenium_manager(driver_path: str = "", browser_version: str = None, browser_path: str = None, proxy=None):
    """
    使用SeleniumManager自动下载driver
    :driver_path       str      driver路径，若不指定driver路径则让管理器自动指定路径
    :browser_version   str      浏览器版本，只需要主版本号。不填则不指定
    :browser_path      str      浏览器exe路径。不填则不指定
    :proxy             dict     下载时使用的代理
    res:
        str/None       driver路径，若无法自定管理则返回None
    """
    if not all([_f_selenium_version_ >= 4, _s_selenium_version_ >= 13]):
        warnings.warn("警告！selenium4.13以下版本不自带SeleniumManager类或存在bug！")
        return None
    # if os.path.isfile('chromedriver.exe'):
    #     # 新版selenium会把driver下载到当前运行目录（4.13）
    #     os.remove('chromedriver.exe')
    M = SeleniumManager()
    # exe路径
    manager_exe_path = M.get_binary()
    # 标准参数
    args = [str(manager_exe_path), "--browser", "chrome"]
    # 指定浏览器版本
    if browser_version:
        args.append("--browser-version")
        args.append(str(browser_version))
    # 浏览器exe路径
    if browser_path:
        args.append("--browser-path")
        args.append(str(browser_path))
    result = M.run(args)
    executable = result["message"]
    print(f"自动下载的driverpath: {executable}")
    if driver_path and (os.path.isfile(driver_path) and not os.path.samefile(executable, driver_path)):
        shutil.move(executable, driver_path)
        executable = driver_path
    return executable


def check_chrome_open_port(port: str):
    """检测chrome是否开放对应端口"""
    return check_open_port("chrome.exe", port)


def check_open_port(exe_name: str, port: str):
    """检测开放对应端口"""
    ports = []
    # 检测所有chrome的pid
    res = os.popen(f"tasklist|findstr {exe_name}").read()
    ress = res.split("\n")
    p_ids = []
    for _res in ress:
        if not _res:
            continue
        try:
            name, pid, _, _, size = re.findall(f"({exe_name})[ ]+(\d+)[ ]+([\S]+)[ ]+(\d+)[ ]+([\d,]+[ ]+[\S+])", _res)[0]
        except Exception as e:
            # 远程上无法加测端口
            continue
        p_ids.append(pid)
    # 检测所有pid的开放端口
    for pid in p_ids:
        res2 = os.popen(f"netstat -ano |findstr {pid}|findstr TCP").read()
        res2s = res2.split("\n")
        for _res2 in res2s:
            if not _res2:
                continue
            try:
                _, server, _pid = re.findall("(\S+)[ ]+([\d\.:\*\[\]]+)[ ]+(?:[\d\.:\*\[\]]+)[ ]+(?:\S+)[ ]+(\d+)", _res2)[0]
            except:
                # ip6是无效的
                continue
            if _pid != pid:
                continue
            # 只有127.0.0.1开头才是开放端口
            if "127.0.0.1" in server:
                ports.append(server.split(":")[-1])
            # print(server)
    return str(port) in ports


def startup_chrome_no_driver(chromepath="auto", port=9222):
    """
    非driver模式,带调试端口的启动chrome
    参数：
        chromepath   str     浏览器启动器路径,auto则自动启动
        port         bool    调试端口，默认9222
    """
    if chromepath == "auto":
        chromepath = get_chrome_path()
    assert os.path.isfile(chromepath), "未定义或无法识别chrome路径"
    # 注意：一个chrome只能映射一个端口！同时多个chrome只以最初启动的端口为准。
    # 启动前必须关闭所有chrome才能启动。否则新起chrome依然无法连接
    os.popen(f'"{chromepath}" --remote-debugging-port={port} --no-sandbox')


def kill_chrome(all_user=False, web_filename="chrome.exe"):
    """
    无差别结束chrome进程
    参数:
        all_user       bool    是否结束所有账号的进程（需要管理员权限）
        web_filename   str     浏览器启动器名
    返回：
        str     返回信息
    """
    try:
        if all_user:
            command = f"taskkill /F /IM {web_filename}"
        else:
            command = f'taskkill /F /FI "USERNAME eq {getpass.getuser()}" /IM {web_filename}'
        out = os.popen(command).read()
        return out
    except Exception as e:
        print(str(e))
        return ""


def kill_chrome_driver(all_user=False, driver_filename="chromedriver.exe"):
    """
    无差别杀死chromedriver进程（仅当前账号）
    参数:
        all_user         bool    是否结束所有账号的进程（需要管理员权限）
        driver_filename  str     driver的文件名
    返回：
        str     返回信息
    """
    try:
        if all_user:
            command = f"taskkill /F /IM {driver_filename}"
        else:
            command = f'taskkill /F /FI "USERNAME eq {getpass.getuser()}" /IM {driver_filename}'
        out = os.popen(command).read()
        return out
    except Exception as e:
        print(str(e))
        return ""


def clear_appdata():
    """
    删除所有缓存，注意包括登录cookies和历史数据
    参数:
        无
    返回：
        bool
    """
    os.system(rf"del /F /S /Q C:\Users\{getpass.getuser()}\AppData\*")
    return True


def not_show_session_recovery(userdata_dir: str = ""):
    """
    设置不显示“恢复页面”窗口
    :userdata_dir     str     用户数据目录，如果不填则尝试自动识别
    """
    if not userdata_dir:
        if "window" in platform.system().lower():
            _path = fetch_user_data_dir()
            file_path = os.path.join(_path, r"Default\Preferences")
        else:
            file_path = rf"/home/{getpass.getuser()}/.config/chromium/Default/Preferences"
    else:
        file_path = os.path.join(userdata_dir, "Default", "Preferences")
    if os.path.isfile(file_path):
        with open(file_path, "r+", encoding="utf-8") as f:
            txt = f.read()
            if '"exit_type":"Crashed"' in txt:
                txt = txt.replace('"exit_type":"Crashed"', '"exit_type":"Normal"')
                f.write(txt)
            f.close()
        return True
    else:
        warnings.warn(f"警告:不存在文件{file_path},请填入正确chrome用户数据目录路径")
        return False
